﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;

namespace SDL
{
    using DockElementList = ObservableList<IDockElement>;
    using DockWindowList = ObservableList<DockWindow>;

    // The base of the dock layout, contains the stage, any number of elements 
    // docked around it, and any number of floating windows.
    [ContentProperty("Elements")]
    public class DockManager : FrameworkElement, IDockContainer
    {
        /////////////////////////////////////////////////////////////////////////////
        // Attached Properties

        public static readonly DependencyProperty EdgeProperty =
            DependencyProperty.RegisterAttached("Edge",
                                                typeof(Edge),
                                                typeof(DockManager));

        public static Edge GetEdge(DependencyObject element)
        {
            return (Edge)element.GetValue(EdgeProperty);
        }
        public static void SetEdge(DependencyObject element, Edge edge)
        {
            element.SetValue(EdgeProperty, edge);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Properties

        public Window Window { get; private set; }
        public DockElementList Elements { get; set; }
        public DockWindowList DockWindows { get; set; }

        /////////////////////////////////////////////////////////////////////////////
        // Default Constructor

        public DockManager()
        {
            _stage = new FileGroup();
            AddLogicalChild(_stage);
            AddVisualChild(_stage);

            Elements = new DockElementList();
            Elements.ListChanged += new ListChangedEventHandler(Elements_Changed);

            DockWindows = new DockWindowList();

            Loaded += new RoutedEventHandler(DockManager_Loaded);
        }

        /////////////////////////////////////////////////////////////////////////////
        // IElement

        public FrameworkElement AsFrameworkElement()
        {
            return this;
        }

        /////////////////////////////////////////////////////////////////////////////
        // IDockContainer

        public void Remove(IDockElement element, IDockElement replacement)
        {
            int index = Elements.IndexOf(element);
            Elements.RemoveAt(index);
            if( replacement != null )
            {
                // get the edge of the removed element
                Edge edge = GetEdge(element.AsFrameworkElement());

                // set the edge of the replacement element
                SetEdge(replacement.AsFrameworkElement(), edge);

                // add the new element to the list
                Elements.Insert(index, replacement);
            }
        }

        public bool StartDrag(IDockElement element)
        {
            return false;
        }

        public void AutoSize(Size size)
        {
        }

        public void TearOff(IDockElement element, Point offset, Size size)
        {
            // check if the element is an immediate child of the frame
            foreach( IDockElement dockElement in Elements )
            {
                if( dockElement == element )
                {
                    // remove the element from the frame
                    Elements.Remove(dockElement);
                    break;
                }
            }

            // create a new dock window
            DockWindow window = new DockWindow(element, offset, size);
            window.Show(this);
            DockWindows.Add(window);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Internal Methods TODO: these are rather iffy...

        internal void Window_CheckDrop(IDockElement dockElement, Point point)
        {
            for( int i = 1; i < DockWindows.Count; ++i )
            {
                DockWindow window = DockWindows[i];

                if( window.CheckDrop(dockElement, point) ) return;
            }

            if( VisualTreeHelper.HitTest(this, PointFromScreen(point)) != null )
            {
                for( int i = 0; i < Elements.Count; ++i )
                {
                    IDockElement element = Elements[i];
                    if( element.CheckDrop(dockElement, point) ) return;
                }

                if( _stage.CheckDrop(dockElement, point) ) return;
            }
        }

        internal void Window_BringToFront(DockWindow window)
        {
            // move the window to the beginning of the list
            DockWindows.Move(window, 0);
        }

        internal void Window_TearOff(IDockElement element, Point offset, Size size)
        {
            // create a new dock window
            DockWindow window = new DockWindow(element, offset, size);
            window.Show(this);
            DockWindows.Add(window);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Event Handlers

        private void DockManager_Loaded(object sender, RoutedEventArgs e)
        {
            // get the owning window
            Window = Window.GetWindow(this);

            // Update the orientation of the elements
            foreach( IDockElement element in Elements )
            {
                element.UpdateOrientation();
            }

            if( DesignerProperties.GetIsInDesignMode(this) )
            {
                // get rid of tool windows in design mode
                DockWindows.Clear();
            }
            else
            {
                // create any tool windows added in xaml
                foreach( DockWindow window in DockWindows.Copy() )
                {
                    window.Show(this);
                }
            }
        }

        private void Elements_Changed(object sender, ListChangedEventArgs e)
        {
            // TODO: I may be able to add this function by handling loaded and 
            // preventing the list from changes except through internal functions
            if( e.NewItems != null )
            {
                foreach( IDockElement element in e.NewItems )
                {
                    // add the element to the logical and visual trees
                    AddLogicalChild(element);
                    AddVisualChild(element.AsFrameworkElement());
                }
            }
            if( e.OldItems != null )
            {
                foreach( IDockElement element in e.OldItems )
                {
                    // remove the element from the logical and visual trees
                    RemoveLogicalChild(element);
                    RemoveVisualChild(element.AsFrameworkElement());
                }
                InvalidateMeasure();
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Private Data

        private FileGroup _stage;
        //private Window _previewWindow;

        /////////////////////////////////////////////////////////////////////////////
        // FrameworkElement

        protected override int VisualChildrenCount
        {
            get { return 1 + Elements.Count; }
        }

        protected override IEnumerator LogicalChildren
        {
            get
            {
                yield return _stage;
                foreach( IDockElement element in Elements )
                {
                    yield return element;
                }
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if( index == 0 ) { return _stage; }
            return Elements[index - 1].AsFrameworkElement();
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            // find the size desired with infinite space
            Size infiniteSize = new Size(double.PositiveInfinity,
                                         double.PositiveInfinity);

            // find the size desired by the stage
            _stage.Measure(infiniteSize);
            Size desiredSize = _stage.DesiredSize;

            // add the size desired by each element
            for( int i = Elements.Count - 1; i >= 0; --i )
            {
                IDockElement element = Elements[i];
                element.Measure(infiniteSize);
                switch( GetEdge(element.AsFrameworkElement()) )
                {
                case Edge.Left:
                case Edge.Right:
                    desiredSize.Width += element.DesiredSize.Width;
                    desiredSize.Height = Math.Max(desiredSize.Height,
                                                  element.DesiredSize.Height);
                    break;
                case Edge.Top:
                case Edge.Bottom:
                    desiredSize.Width = Math.Max(desiredSize.Width,
                                                 element.DesiredSize.Width);
                    desiredSize.Height += element.DesiredSize.Height;
                    break;
                }
            }

            // return the total desired size
            return desiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            Rect finalSpace = new Rect(finalSize);
            for( int i = 0; i < Elements.Count; ++i )
            {
                IDockElement element = Elements[i];
                Rect currentSpace = finalSpace;
                Size desiredSize = element.DesiredSize;
                switch( GetEdge(element.AsFrameworkElement()) )
                {
                case Edge.Left:
                    currentSpace.Width = desiredSize.Width;
                    finalSpace.X += desiredSize.Width;
                    finalSpace.Width -= desiredSize.Width;
                    break;
                case Edge.Top:
                    currentSpace.Height = desiredSize.Height;
                    finalSpace.Y += desiredSize.Height;
                    finalSpace.Height -= desiredSize.Height;
                    break;
                case Edge.Right:
                    currentSpace.X = currentSpace.Right - desiredSize.Width;
                    currentSpace.Width = desiredSize.Width;
                    finalSpace.Width -= desiredSize.Width;
                    break;
                case Edge.Bottom:
                    currentSpace.Y = currentSpace.Height - desiredSize.Height;
                    currentSpace.Height = desiredSize.Height;
                    finalSpace.Height -= desiredSize.Height;
                    break;
                }
                element.Arrange(currentSpace);
            }
            _stage.Arrange(finalSpace);
            return finalSize;
        }
    }
}
